1   /*
2    * Copyright (c) 2007, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  package com.sun.media.sound;
26  
27  import java.io.EOFException;
28  import java.io.IOException;
29  import java.io.InputStream;
30  
31  import javax.sound.sampled.AudioFormat;
32  import javax.sound.sampled.AudioInputStream;
33  
34  /**
35   * A jitter corrector to be used with SoftAudioPusher.
36   *
37   * @author Karl Helgason
38   */
39  public class SoftJitterCorrector extends AudioInputStream {
40  
41      private static class JitterStream extends InputStream {
42  
43          static int MAX_BUFFER_SIZE = 1048576;
44          boolean active = true;
45          Thread thread;
46          AudioInputStream stream;
47          // Cyclic buffer
48          int writepos = 0;
49          int readpos = 0;
50          byte[][] buffers;
51          Object buffers_mutex = new Object();
52  
53          // Adapative Drift Statistics
54          int w_count = 1000;
55          int w_min_tol = 2;
56          int w_max_tol = 10;
57          int w = 0;
58          int w_min = -1;
59          // Current read buffer
60          int bbuffer_pos = 0;
61          int bbuffer_max = 0;
62          byte[] bbuffer = null;
63  
64          public byte[] nextReadBuffer() {
65              synchronized (buffers_mutex) {
66                  if (writepos > readpos) {
67                      int w_m = writepos - readpos;
68                      if (w_m < w_min)
69                          w_min = w_m;
70  
71                      int buffpos = readpos;
72                      readpos++;
73                      return buffers[buffpos % buffers.length];
74                  }
75                  w_min = -1;
76                  w = w_count - 1;
77              }
78              while (true) {
79                  try {
80                      Thread.sleep(1);
81                  } catch (InterruptedException e) {
82                      //e.printStackTrace();
83                      return null;
84                  }
85                  synchronized (buffers_mutex) {
86                      if (writepos > readpos) {
87                          w = 0;
88                          w_min = -1;
89                          w = w_count - 1;
90                          int buffpos = readpos;
91                          readpos++;
92                          return buffers[buffpos % buffers.length];
93                      }
94                  }
95              }
96          }
97  
98          public byte[] nextWriteBuffer() {
99              synchronized (buffers_mutex) {
100                 return buffers[writepos % buffers.length];
101             }
102         }
103 
104         public void commit() {
105             synchronized (buffers_mutex) {
106                 writepos++;
107                 if ((writepos - readpos) > buffers.length) {
108                     int newsize = (writepos - readpos) + 10;
109                     newsize = Math.max(buffers.length * 2, newsize);
110                     buffers = new byte[newsize][buffers[0].length];
111                 }
112             }
113         }
114 
115         public JitterStream(AudioInputStream s, int buffersize,
116                 int smallbuffersize) {
117             this.w_count = 10 * (buffersize / smallbuffersize);
118             if (w_count < 100)
119                 w_count = 100;
120             this.buffers
121                     = new byte[(buffersize/smallbuffersize)+10][smallbuffersize];
122             this.bbuffer_max = MAX_BUFFER_SIZE / smallbuffersize;
123             this.stream = s;
124 
125 
126             Runnable runnable = new Runnable() {
127 
128                 public void run() {
129                     AudioFormat format = stream.getFormat();
130                     int bufflen = buffers[0].length;
131                     int frames = bufflen / format.getFrameSize();
132                     long nanos = (long) (frames * 1000000000.0
133                                             / format.getSampleRate());
134                     long now = System.nanoTime();
135                     long next = now + nanos;
136                     int correction = 0;
137                     while (true) {
138                         synchronized (JitterStream.this) {
139                             if (!active)
140                                 break;
141                         }
142                         int curbuffsize;
143                         synchronized (buffers) {
144                             curbuffsize = writepos - readpos;
145                             if (correction == 0) {
146                                 w++;
147                                 if (w_min != Integer.MAX_VALUE) {
148                                     if (w == w_count) {
149                                         correction = 0;
150                                         if (w_min < w_min_tol) {
151                                             correction = (w_min_tol + w_max_tol)
152                                                             / 2 - w_min;
153                                         }
154                                         if (w_min > w_max_tol) {
155                                             correction = (w_min_tol + w_max_tol)
156                                                             / 2 - w_min;
157                                         }
158                                         w = 0;
159                                         w_min = Integer.MAX_VALUE;
160                                     }
161                                 }
162                             }
163                         }
164                         while (curbuffsize > bbuffer_max) {
165                             synchronized (buffers) {
166                                 curbuffsize = writepos - readpos;
167                             }
168                             synchronized (JitterStream.this) {
169                                 if (!active)
170                                     break;
171                             }
172                             try {
173                                 Thread.sleep(1);
174                             } catch (InterruptedException e) {
175                                 //e.printStackTrace();
176                             }
177                         }
178 
179                         if (correction < 0)
180                             correction++;
181                         else {
182                             byte[] buff = nextWriteBuffer();
183                             try {
184                                 int n = 0;
185                                 while (n != buff.length) {
186                                     int s = stream.read(buff, n, buff.length
187                                             - n);
188                                     if (s < 0)
189                                         throw new EOFException();
190                                     if (s == 0)
191                                         Thread.yield();
192                                     n += s;
193                                 }
194                             } catch (IOException e1) {
195                                 //e1.printStackTrace();
196                             }
197                             commit();
198                         }
199 
200                         if (correction > 0) {
201                             correction--;
202                             next = System.nanoTime() + nanos;
203                             continue;
204                         }
205                         long wait = next - System.nanoTime();
206                         if (wait > 0) {
207                             try {
208                                 Thread.sleep(wait / 1000000L);
209                             } catch (InterruptedException e) {
210                                 //e.printStackTrace();
211                             }
212                         }
213                         next += nanos;
214                     }
215                 }
216             };
217 
218             thread = new Thread(runnable);
219             thread.setDaemon(true);
220             thread.setPriority(Thread.MAX_PRIORITY);
221             thread.start();
222         }
223 
224         public void close() throws IOException {
225             synchronized (this) {
226                 active = false;
227             }
228             try {
229                 thread.join();
230             } catch (InterruptedException e) {
231                 //e.printStackTrace();
232             }
233             stream.close();
234         }
235 
236         public int read() throws IOException {
237             byte[] b = new byte[1];
238             if (read(b) == -1)
239                 return -1;
240             return b[0] & 0xFF;
241         }
242 
243         public void fillBuffer() {
244             bbuffer = nextReadBuffer();
245             bbuffer_pos = 0;
246         }
247 
248         public int read(byte[] b, int off, int len) {
249             if (bbuffer == null)
250                 fillBuffer();
251             int bbuffer_len = bbuffer.length;
252             int offlen = off + len;
253             while (off < offlen) {
254                 if (available() == 0)
255                     fillBuffer();
256                 else {
257                     byte[] bbuffer = this.bbuffer;
258                     int bbuffer_pos = this.bbuffer_pos;
259                     while (off < offlen && bbuffer_pos < bbuffer_len)
260                         b[off++] = bbuffer[bbuffer_pos++];
261                     this.bbuffer_pos = bbuffer_pos;
262                 }
263             }
264             return len;
265         }
266 
267         public int available() {
268             return bbuffer.length - bbuffer_pos;
269         }
270     }
271 
272     public SoftJitterCorrector(AudioInputStream stream, int buffersize,
273             int smallbuffersize) {
274         super(new JitterStream(stream, buffersize, smallbuffersize),
275                 stream.getFormat(), stream.getFrameLength());
276     }
277 }